package com.hup.utils.commons.io;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.hup.utils.commons.JavaUtil;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.util.Assert;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.logging.Logger;

/**
 * 配置基础类;
 * 忽略属性:@JSONField(serialize = false)
 *
 * @author hugan
 * @date 2019/8/11
 */
public interface BaseConfig<C> {

    class Path {
        /**
         * 修改全局配置文件根路径,用途如:
         * .开发时子模块问题(建议通过设置[idea->run config->working directory=$MODULE_DIR$])
         * .其他某些项目特殊需求
         */
        @Setter
        @Getter
        private static String baseDir = "./config/";
    }

    /**
     * 转Json字符串时,是否保留value是空的mapKey: {@link SerializerFeature#WriteMapNullValue}
     *
     * @return 默认:{@link JSON}默认不保留
     */
    default boolean writeMapNullValue() {
        return false;
    }

    default boolean debug() {
        return false;
    }

    /**
     * @return 配置文件名, 默认是[/config]下的[实现类类名].config
     */
    default String configPath() {
        return Path.baseDir + this.getClass().getSimpleName() + ".config";
    }

    /**
     * 获取[配置文件]的配置对象
     *
     * @return nullable
     */
    @SuppressWarnings("unchecked")
    default C readConfigFile() {
        String path = configPath();
        Assert.hasText(path, "配置路径为空");
        File file = new File(path);
        Logger logger = Logger.getLogger(this.getClass().getName());
        if (!file.exists()) {
            if (logger != null) {
                logger.info("无配置文件:" + path);
            }
            return null;
        }
        String str = null;
        try {
            str = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
            Type type = JavaUtil.getInterfaceType(this, BaseConfig.class)[0];
            return (C) JSON.parseObject(str, type);
        } catch (Exception e) {
            return handleReadFileErr(e, str, file);
        }
    }

    default C handleReadFileErr(Exception e, String str, File file) {
        String errMsg = this.getClass().getSimpleName() + "配置文件异常:[" + e.getMessage() + "];";
        if (!(e instanceof JSONException)) errMsg += str;
        Logger logger = Logger.getLogger(this.getClass().getName());
        if (logger != null) {
            logger.severe(errMsg);
        }

        //备份异常配置文件
        try {
            FileUtils.copyFile(file, new File(file.getAbsolutePath() + "_err"));
        } catch (IOException e1) {
            if (logger != null) {
                logger.severe("备份异常配置文件错误:" + e1.getMessage());
            }
        }
        return null;
    }

    /**
     * 保存配置
     */
    default void save() {
        Logger logger = Logger.getLogger(this.getClass().getName());
        if (debug() && logger != null) {
            logger.info("保存:" + getClass().getSimpleName());
        }
        String str;
        if (!writeMapNullValue()) {
            str = JSON.toJSONString(this, true);
        } else {
            str = JSON.toJSONString(this, SerializerFeature.WriteMapNullValue, SerializerFeature.PrettyFormat);
        }
        String path = configPath();
        Assert.hasText(path, "配置路径为空");
        File file = new File(path);
        try {
            FileUtils.write(file, str, StandardCharsets.UTF_8);
        } catch (IOException e) {
            if (logger != null) {
                logger.severe("保存错误:" + e.getMessage());
            }
        }
    }

    /**
     * 加载文件的配置到本对象中
     */
    @SuppressWarnings("unchecked")
    default C loadFromFile() {
        C file = readConfigFile();
        if (file != null) BeanUtils.copyProperties(file, this);
        boolean needSave = afterLoadFromFileOrReset(file);
        if (debug()) Logger.getLogger(this.getClass().getName()).info(this.toString());

        if (needSave) save();
        return (C) this;
    }

    /**
     * 用途:
     * .{@link #loadFromFile()}末尾调用:检查或初始化属性取值,决定是否保存配置
     * .在重置配置功能中(实例化后,不读取文件配置),检查或初始化属性取值
     *
     * @param fileConfig nullable,从文件读取到的配置对象
     * @return needSave, 是否需要保存一次配置
     */
    default boolean afterLoadFromFileOrReset(C fileConfig) {
        return false;
    }

}
